<?php
/**
 * @file Ucenterm.php
 * @synopsis  用户积分、经验
 * @author tianyu
 * @version 1.0
 * @date 2015-10-13
 */

defined('BASEPATH') OR exit('No direct script access allowed');
class Ucenterm extends CI_Model
{
    /**
     * @var array 积分规则
     */
    public $rules;
    /**
     * @var array 经验等级
     */
    public $exp_levels;

    private $db_name_rules = "ml_ucenter_rules";
    private $db_name_exp_levels = "ml_exp_level_rules";
    private $db_name_log = "ml_ucenter_log";

    function __construct()
    {
        $this->load->library('usercenter');
        $this->rules = self::readRules();
        $this->exp_levels = self::readExpLevels();
    }

    /**
     * 获取积分、威望规则，按起始分数从地至高排列
     *
     * @return array
     */
    public function readRules() {
        $reules = $this->db->select('*')->from($this->db_name_rules)
            ->where('in_use', "1")->get()->result_array();
        $result = array();
        foreach ($reules as $rule) {
            $result[$rule["action_value"]] = $rule;
        }
        return $result;

    }

    /**
     * 获取经验（威望）级别列表
     *
     * @return array
     */
    public function readExpLevels() {
        $levels = $this->db->select('*')
            ->from($this->db_name_exp_levels)->get()->result_array();
        $result = array();
        foreach ($levels as $level) {
            $result[$level["level"]] = $level;
        }
        ksort($result);
        return $result;
    }

    /**
     * 获取用户积分威望值
     *
     * @param $uid
     * @param $type
     * @return int
     */
    public function getUserBonus($uid, $type) {
        $result = $this->db->select("bonus_result")
            ->where(array("uid"=>$uid, "type"=>$type))->order_by("log_id","desc")
            ->limit(1,0)->get($this->db_name_log)->row_array();
        if (empty($result)) {
            return 0;
        }
        return $result["bonus_result"];
    }

    /**
     * 增加db记录
     *
     * @param $uid
     * @param $type
     * @param $action_value
     * @param $bonus
     * @param $relate_value
     * @return int
     */
    private function addUcenterActionLog($uid, $type, $action_value, $bonus, $relate_value) {
        $bonus_old = $this->getUserBonus($uid, $type);
        $bonus_result = $bonus_old + $bonus;
        if ($bonus_result<0) {
            $bonus_result = 0;
        }
        $data = array(
            "uid" => $uid,
            "type" => $type,
            "action_value" => $action_value,
            "bonus" => $bonus,
            "atime" => time(),
            "bonus_old" => $bonus_old,
            "bonus_result" => $bonus_result
        );
        if ($relate_value) {
            $data["relate_value"] = $relate_value;
        }
        $result = $this->db->insert($this->db_name_log, $data);
        $log_id = 0;
        if ($result) {
            $log_id = $this->db->insert_id();
        }
        return $log_id;
    }

    private function getLastAction($uid, $type, $action_value, $relate_value=0) {
        $whe = array(
            "uid" => $uid,
            "type" => $type,
            "action_value" => $action_value,
            "is_canceled" => 0,
        );
        if ($relate_value) {
            $whe["relate_value"] = $relate_value;
        }
        $this->db->select("*")->from($this->db_name_log);
        $this->db->where($whe);
        $this->db->order_by("log_id", "desc")->limit(1);
        $query = $this->db->get()->row_array();
        return $query;
    }

    private function getActionsByRange($uid, $type, $action_value, $relate_value=0, $start=0, $end=0) {
        $whe = array(
            "uid" => $uid,
            "type" => $type,
            "action_value" => $action_value,
            "is_canceled" => 0,
        );
        if ($relate_value) {
            $whe["relate_value"] = $relate_value;
        }
        if ($start) {
            $whe["atime>= "] = $start;
        }
        if ($end) {
            $whe["atime< "] = $end;
        }
        $this->db->from($this->db_name_log);
        $this->db->where($whe);
        return $this->db->get()->result_array();
    }

    private function getActionCount($uid, $type, $action_value, $relate_value=0, $start=0, $end=0) {
        $whe = array(
            "uid" => $uid,
            "type" => $type,
            "action_value" => $action_value,
            "is_canceled" => 0,
        );
        if ($relate_value) {
            $whe["relate_value"] = $relate_value;
        }
        if ($start) {
            $whe["atime>= "] = $start;
        }
        if ($end) {
            $whe["atime< "] = $end;
        }
        $this->db->from($this->db_name_log);
        $this->db->where($whe);
        return $this->db->count_all_results();
    }

    private function markActionLogCanceled($log_id) {
        $this->db->set("is_canceled", 1);
        $this->db->where("log_id", $log_id);
        $result = $this->db->update("ml_ucenter_log");
        return $result;
    }

    private function getTimeRangeByPeriod($period) {
        if ($period == Usercenter::PERIOD_EVERY_MIN) {
            $start = date("Y-m-d H:i:00", time());
            $end = date("Y-m-d H:i:59", time());
            return array($start, $end);
        } elseif ($period == Usercenter::PERIOD_EVERY_HOUR) {
            $start = date("Y-m-d H:00:00", time());
            $end = date("Y-m-d H:59:59", time());
            return array($start, $end);
        } elseif ($period == Usercenter::PERIOD_EVERY_DAY) {
            $start = date("Y-m-d 00:00:00", time());
            $end = date("Y-m-d 23:59:59", time());
            return array($start, $end);
        } elseif ($period == Usercenter::PERIOD_EVERY_WEEK) {
            $now = time();
            $w = strftime('%u', time());
            $start = date('Y-m-d 00:00:00',$now - ($w - 1) * 86400);
            $end = date('Y-m-d 23:59:59', $now + (7 - $w) * 86400);
            return array($start, $end);
        }
        return array(0, 0);
    }

    private function isLimitted($type, $action_value, $rule, $uid, $relate_value) {
        $limit = $rule["limit"];
        $period = $rule["period"];
        if ($limit == 0) {
            return false;
        } elseif ($limit == 1) {
            $action = $this->getLastAction($uid, $type, $action_value, $relate_value);
            if ($action) {
                return true;
            }
        } elseif (isset(Usercenter::$period_restrict[$period])) {
            $range = $this->getTimeRangeByPeriod($period);
            if ($limit <= $this->getActionCount($uid, $type, $action_value, $relate_value, $range[0], $range[1])) {
                return true;
            }
        }
        return false;
    }

    private function basic_rule_check($rule, $relate_value) {
        if (empty($rule)) {
            return "没有定义这种积分规则";
        }
        # 操作需传关联id
        if ($rule["with_relate"] && empty($relate_value)) {
            return "操作需要关联其它字段值";
        }
        return "";
    }

    private function check($uid, $type, $rule, $action_value, $relate_value=0) {
        # 如果是取消操作，需要检查待取消记录是否存在
        if (isset(Usercenter::$anti_actions[$action_value])) {
            $anti_action_value = Usercenter::$anti_actions[$action_value];
            if (!$this->getLastAction($uid, $type, $anti_action_value, $relate_value)) {
                return "没有可被取消的记录";
            }
        }

        # 检查是否已达操作次数限制
        if ($this->isLimitted($type, $action_value, $rule, $uid, $relate_value)) {
            return "已达操作数上限";
        }

        return "";
    }

    private function calculate_bonus($uid, $type, $rule, $action_value, $relate_value, $bonus_value) {
        // 可以按自定义规则返回积分值

        if ($bonus_value) {
            return $bonus_value;
        }

        # 如果是取消操作，要将之前的操作置为已取消
        if (isset(Usercenter::$anti_actions[$action_value])) {
            $anti_action_value = Usercenter::$anti_actions[$action_value];
            $last_log = $this->getLastAction($uid, $type, $anti_action_value, $relate_value);
            return $last_log["bonus"];
        }

        if ($type == Usercenter::REWARD_BONUS) {
            return $rule["point"];
        } else {
            return $rule["exp"];
        }
    }

    /**
     * 获取限定周期内已有的操作记录
     *
     * @param $uid
     * @param int $type
     * @param $action_value
     * @param int $relate_value
     * @return string
     */
    public function getBonusRecords($uid, $type=Usercenter::REWARD_EXP, $action_value, $relate_value=0) {
        $rule = $this->rules[$action_value];

        $error_msg = $this->basic_rule_check($rule, $relate_value);
        if ($error_msg) {
            return $error_msg;
        }

        $range = $this->getTimeRangeByPeriod($rule["period"]);
        return $this->getActionsByRange($uid, $type, $action_value, $relate_value, $range[0], $range[1]);
    }

    /**
     * 增加积分变动
     */
    public function addBonus($uid, $type=Usercenter::REWARD_EXP, $action_value, $relate_value=0, $bonus_value=0) {
        $rule = $this->rules[$action_value];

        $error_msg = $this->basic_rule_check($rule, $relate_value);
        if ($error_msg) {
            return $error_msg;
        }

        $error_msg = $this->check($uid, $type, $rule, $action_value, $relate_value);
        if ($error_msg) {
            return $error_msg;
        }

        # 如果是取消操作，要将之前的操作置为已取消
        if (isset(Usercenter::$anti_actions[$action_value])) {
            $anti_action_value = Usercenter::$anti_actions[$action_value];
            $last_log = $this->getLastAction($uid, $type, $anti_action_value, $relate_value);
            $this->markActionLogCanceled($last_log["log_id"]);
        }
        # 如果是取消后再次操作，需要干掉取消操作
        $reverse_actions = array_flip(Usercenter::$anti_actions);
        if (isset($reverse_actions[$action_value])) {
            $anti_action_value = $reverse_actions[$action_value];
            $last_log = $this->getLastAction($uid, $type, $anti_action_value, $relate_value);
            $this->markActionLogCanceled($last_log["log_id"]);
        }

        $bonus = $this->calculate_bonus($uid, $type, $rule, $action_value, $relate_value, $bonus_value);
        $log_id = $this->addUcenterActionLog($uid, $type, $action_value, $bonus, $relate_value);
        if (empty($log_id)) {
            return "数据执行操作失败";
        }

        return $log_id;
    }


    /**
     * 获取用户经验等级
     *
     * @param $uid
     * @return array
            level: {
                lid: 1, // 记录id，无用
                level: 1, // 等级值
                lname: "初级用户", // 等级名称
                lstart: 0, // 经验最低值
                lend: 100, // 经验阈值
                ldesc: "", // 描述
                exp: 50, // 当前经验值
            }
     */
    public function getUserExpLevel($uid) {
        $exp = $this->getUserBonus($uid, Usercenter::REWARD_EXP);

        $result = array();
        foreach($this->exp_levels as $level) {
            if ($exp < $level["lstart"]) {
                continue;
            }
            if ($exp < $level["lend"]) {
                $result = $level;
                break;
            }
            if (empty($level["lend"])) {
                $result = $level;
            }
        }
        if (empty($result)) {
            // 未取到用户等级，返回0级
            $result = $this->exp_levels[0];
        }
        $result["exp"] = $exp;
        return $result;
    }


    /**
     * 获取用户动作信息
     *
     * @param $uid
     * @param $type
     * @param int $page
     * @param int $page_size
     */
    public function getActionsByPage($uid, $type, $page=1, $page_size=Usercenter::HIS_PAGE_SIZE) {
        $whe = array(
            "uid" => $uid,
            "type" => $type,
            "is_canceled" => 0,
        );
        $start = ($page - 1) * $page_size;
        $this->db->from($this->db_name_log)->where($whe)
            ->order_by("log_id", "desc")->limit($page_size, $start);
        return $this->db->get()->result_array();
    }
}